package com.webex.ta.hydra.core.action;

import com.webex.ta.hydra.core.*;
import com.webex.ta.hydra.gui.table.celleditor.HttpParamEditor;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.protocol.Protocol;

import javax.swing.table.TableCellEditor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-9-19
 * Time: 13:28:55
 */
public class HttpRequest extends AbstractAction {
    public static final String PROTOCOL_HTTP = "http";
    public static final String POST = "POST";
    public static final String PUT = "PUT";
    public static final String GET = "GET";
    public static final String HEADER_CONNECTION = "Connection";
    public static final String CONNECTION_CLOSE = "close";
    public static final String KEEP_ALIVE = "keep-alive";
    public static final String HEADER_CONTENT_ENCODING = "content-encoding";
    public static final String HEADER_CONTENT_TYPE = "Content-Type";
    public static final String ENCODING_GZIP = "gzip";

    private static final String QRY_SEP = "&";

    private static final String QRY_PFX = "?";

    private String url;
    private String method;
    private List<Entry> arguments = new LinkedList<Entry>();

    private static ParamDescriptor[] desc = new ParamDescriptor[4];

    static {
        desc[0] = new ParamDescriptor("URL");
        desc[1] = new ParamDescriptor(ParamType.LIST, "method", false, new String[]{GET, POST, PUT});
        desc[2] = new ParamDescriptor(ParamType.HTTP, "parameters", true, null);
        desc[3] = new ParamDescriptor(ParamType.HTTP, "parameters", true, null);
    }


    public ParamDescriptor[] getParamDescriptors(Command cmd) {
        return desc;
    }

    @Override
    public TableCellEditor getParameterEditor(Command cmd, int paramIndex) {
        if (paramIndex < 4)
            return super.getParameterEditor(cmd, paramIndex);
        return new HttpParamEditor();
    }

    public void execute(TestResult result, String... args) throws Exception {
        url = args[0];
        method = args[1];
        arguments.clear();
        for (int i = 2; i < args.length; i++) {
            String argument = args[i];
            if (argument == null)
                continue;
            int pos = argument.indexOf("=");
            if (pos < 0)
                continue;
            String key = argument.substring(0, pos);
            String value = argument.substring(pos + 1);
            arguments.add(new Entry(key, value));
        }

        HttpMethodBase httpMethod = null;
        String urlString = getURL();
        if (POST.equals(method)) {
            httpMethod = new PostMethod(urlString);
        } else if (PUT.equals(method)) {
            httpMethod = new PutMethod(urlString);
        } else {
            httpMethod = new GetMethod(urlString);
        }

        HttpClient client = setupConnection(new URL(urlString), httpMethod);

        if (POST.equals(method)) {
            setupParameters((PostMethod) httpMethod);
        }
        result.setRequest(getRequestData(client, httpMethod));
        int statusCode = client.executeMethod(httpMethod);

        result.setResponseCode(String.valueOf(statusCode));
        result.setResponseHeaders(getResponseHeaders(httpMethod));

        InputStream in = httpMethod.getResponseBodyAsStream();
        if (in != null) {// will be null for HEAD
            Header responseHeader = httpMethod.getResponseHeader(HEADER_CONTENT_ENCODING);
            if (responseHeader != null && ENCODING_GZIP.equals(responseHeader.getValue())) {
                in = new GZIPInputStream(in);
            }

            //int contentLength = httpMethod.getResponseContentLength();Not visible ...
            //TODO size ouststream according to actual content length
            ByteArrayOutputStream outstream = new ByteArrayOutputStream(4 * 1024);
            //contentLength > 0 ? contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
            byte[] buffer = new byte[4096];
            int len;
            boolean first = true;// first response
            while ((len = in.read(buffer)) > 0) {
                if (first) { // save the latency
                    first = false;
                }
                outstream.write(buffer, 0, len);
            }

            byte[] bytes = outstream.toByteArray();

            Header contentType = httpMethod.getResponseHeader(HEADER_CONTENT_TYPE);
            String charset;
            if (contentType != null) {
                // charset available in header
                charset = getCharsetFromContentType(contentType.getValue());
            } else {
                // try in html meta
                charset = getCharsetFromHtmlMeta(bytes);
            }
            if (charset != null) {
                try {
                    result.setResponse(new String(bytes, charset));
                } catch (UnsupportedEncodingException e) {
                    result.setResponse(new String(bytes));
                }
            } else {
                result.setResponse(new String(bytes));
            }

            outstream.close();

        }
    }


    private String getURL() {
        StringBuffer result = new StringBuffer();
        result.append(url);
        if (arguments.size() > 0 && GET.equals(method)) {
            if (!url.endsWith(QRY_PFX))
                result.append(QRY_PFX);
            for (int i = 0; i < arguments.size(); i++) {
                Entry argument = arguments.get(i);
                result.append(argument.key).append("=").append(argument.value);
                if (i != arguments.size() - 1)
                    result.append(QRY_SEP);
            }
        }
        return result.toString();
    }

    protected HttpClient setupConnection(URL u, HttpMethodBase httpMethod) throws IOException {
        String urlStr = u.toString();
        URI uri = new URI(urlStr, false);

        String schema = uri.getScheme();
        if (schema == null || schema.length() == 0) {
            schema = PROTOCOL_HTTP;
        }

        Protocol protocol = Protocol.getProtocol(schema);
        String host = uri.getHost();
        int port = uri.getPort();

        HostConfiguration hc = new HostConfiguration();
        hc.setHost(host, port, protocol); // All needed to ensure re-usablility

        HttpClient httpClient = new HttpClient(new SimpleHttpConnectionManager());
        httpClient.setHostConfiguration(hc);
//        httpMethod.setFollowRedirects(true);
        httpMethod.setRequestHeader(HEADER_CONNECTION, KEEP_ALIVE);

        return httpClient;
    }

    private void setupParameters(PostMethod method) {
        for (Entry arg : arguments) {
            method.addParameter(arg.key, arg.value);
        }
    }

    private String getRequestData(HttpClient client, HttpMethodBase method) {
        StringBuffer sb = new StringBuffer();
        sb.append(method.getName()).append(" ");
        if (method.getPath() == null)
            sb.append("/");
        else
            sb.append(method.getPath());

        sb.append("\n");
        for (Header header : method.getRequestHeaders()) {
            sb.append(header.getName()).append(": ").append(header.getValue()).append("\n");
        }

        sb.append("\n");
        if (method instanceof PostMethod) {
            PostMethod post = (PostMethod) method;
            for (NameValuePair pair : post.getParameters()) {
                sb.append(pair.getName()).append("=").append(pair.getValue());
            }
        }
        return sb.toString();
    }

    private String getResponseHeaders(HttpMethodBase method) {
        StringBuffer sb = new StringBuffer();
        for (Header header : method.getResponseHeaders()) {
            sb.append(header.getName()).append(": ").append(header.getValue()).append("\n");
        }
        return sb.toString();
    }


    private String getCharsetFromContentType(String ct) {
        final String CS_PFX = "charset=";
        int cset = ct.toLowerCase().indexOf(CS_PFX);
        if (cset >= 0) {
            String charSet = ct.substring(cset + CS_PFX.length());
            if (charSet.startsWith("\"")) {
                return charSet.substring(1, charSet.length() - 1);
            } else {
                return charSet;
            }
        }
        return null;
    }

    private String getCharsetFromHtmlMeta(byte bytes[]) {
        // only check the leading 1000 bytes
        String html = new String(bytes, 0, Math.min(bytes.length, 1000)).toLowerCase();
        final String METATAG = "<meta http-equiv=\"content-type\" content=\"";
        int tagstart = html.indexOf(METATAG);
        if (tagstart != -1) {
            tagstart += METATAG.length();
            int tagend = html.indexOf("\"", tagstart); // $NON-NLS-1$
            if (tagend != -1) {
                final String ct = new String(bytes, tagstart, tagend - tagstart);
                return getCharsetFromContentType(ct);
            }
        }
        return null;
    }

    private class Entry {
        public String key;
        public String value;

        public Entry(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }


}
